Ansible

Infraestructure Automation Tool

Exponentes:
Andrés Grosso
Carlos Toro
David Montaño

Agenda

Motivación

  • Tareas comunes operaciones
  • Snowflake Server
  • Phoenix Server
  • Automatizar con Scripts
  • Infraestructura como código
  • Puppet

Dependencias

  • Python
    • REPL
    • pip
  • Conceptos Básicos de Secure Shell
    • ssh
    • ssh-keygen
    • ssh-agent
    • ssh-add

Ansible

  • Inventario
  • Hechos / Facts
  • Tareas
  • Módulos
  • Playbooks

Caso Práctico

  • Despliegue en Amazon: Docker + Zookeeper

Conclusiones

Motivación

Tareas comunes operaciones

  • Preparar y aprovisionar infraestructura
  • Desplegar aplicaciones
  • Configurar daemons
  • Asegurar correctitud de ambientes

Tareas comunes operaciones

  • Sistema Operativo Actualizado
  • Parches de Seguridad Actualizados
  • Vivir en modo ssh
  • Almacenar notas sobre la configuración

Snowflake Server

  • Ejecutar manualmente estas tareas
    • Toma tiempo
    • Propenso a errores
  • Dificultades
    • Reproducir el estado de un servidor
    • Actualizarlos sin inducir errores

Phoenix Server

  • Al perder servidores críticos
    • En cuánto tiempo puedo levantar la infraestructura y las aplicaciones que corren en ella.

Automatizar con Scripts

  • Scripts complejos
  • No muy bien documentados
  • ¿Quien entiende bash?

Infraestructura como código

  • Código en un lenguaje de programación o un DSL descriptivo de alto nivel.
  • Descripción de estado de la infraestructura.
    • Paquetes instalados
    • Archivos e configuración con información adecuada
    • Servicios en ejecución
    • Archivos con permisos adecuados

Infraestructura como código

  • Abarca
    • Aprovisionamiento de infraestructura
    • Administración de configuraciones
    • Despliegue de Aplicaciones

Infraestructura como código

  • Infraestructura en mi sistema de control de versiones
  • Idempotencia

Infraestructura como código

  • No deben existir Snowflakes Servers
  • No se deben ejecutar operaciones manualmente

Puppet

  • Agentes en los clientes y un servidor central Puppet Master
  • DSL declarativo en Ruby
  • Instalación de configuraciones pull-based

Puppet

  • Se programa entorno al estado deseado de la infraestructura. Un nodo pasa de un estado actual al estado deseado.
  • Métodos idempotentes llamados resources

Puppet

  • El nodo manda información al servidor sobre su estado actual.
  • El servidor prepara un catálogo con los cambios que deben ser ejecutados en el nodo.
  • El nodo notifica al servidor que la configuración está completa

Dependencias

python REPL

pip

Permite administrar librerías de Python

  • listar librerías instaladas
  • 				  			
    pip list
    				      		
    						
  • buscar dependencias
  • 				  			
    pip search docker
    				      		
    						

pip

Permite administrar librerías de Python

  • instalar dependencias
  • 				  			
    pip install docker-py
    				      		
    						
  • desinstalar dependencias
  • 				  			
    pip uninstall docker-py
    				      		
    						

ssh (cliente / servidor)

  • Protocolo de red que permite intercambiar datos usando un canal seguro entre dos computadores.
  • Basado en criptografía de llave pública:
    • dsa (deprecated)
    • ecdsa
    • ed25519
    • rsa
  • Conociendo un usuario de la máquina remota podemos ingresar a ella.
  • 				  			
    [david@JANUS ~]$ ssh vashy@192.168.16.6
    				      		
    						

ssh-keygen

  • Permite generar uno o varios pares de llaves públicas y privadas.
  • Se recomienda asignarle una contraseña a la llave privada para que no baste con tenerla para ingresar a otras máquinas.
  • Por defecto guarda las llaves en:
    • ~/.ssh/id_rsa
    • ~/.ssh/id_rsa.pub
    				  				
    [david@JANUS ~]$ ssh-keygen -t ed25519
    				      			
    							

ssh-agent

  • Autocompleta las contraseñas cada vez que ssh las solicita en la terminal donde se ejecuta.
  • 			  				
    [david@JANUS ~]$ eval $(ssh-agent)
    			      			
    						
  • Una vez se arranca el agente, se le pueden entregas llaves usando el comando ssh-add.
  • 			  				
    [david@JANUS ~]$ ssh-add
    Enter passphrase for /home/david/.ssh/id_rsa:
    Identity added: /home/david/.ssh/id_rsa (/home/david/.ssh/id_rsa)
    			      			
    						

Inventario

El conjunto de Hosts con sus diferentes agrupamientos y variables que componen la infraestructura. Esta información se escribe en un archivo INI.

Inventario

Hosts
									
#Host
mail.example.com

#Host con puerto específico
badwolf.example.com:5309

#Host con alias
jumper ansible_port=5555 ansible_host=192.168.1.50
								
							

Inventario

Grupos
									
#Grupo dbservers
[dbservers]
one.example.com
two.example.com
three.example.com

#Grupo con rango de números
[webservers_2]
www[01:50].example.com

#Grupo con rango alfabético
[databases]
db-[a:f].example.com
								
							

Inventario

Variables
									
#Variables de Host
[atlanta]
host1 http_port=80 maxRequestsPerChild=808
host2 http_port=303 maxRequestsPerChild=909

#Variables de Grupo
[atlanta:vars]
ntp_server=ntp.atlanta.example.com
proxy=proxy.atlanta.example.com
								
							

Inventario

Grupos, Grupos de Grupos y Variables de Grupos
									
[atlanta]
host1
host2

[raleigh]
host2
host3

[southeast:children]
atlanta
raleigh

[southeast:vars]
some_server=foo.southeast.example.com
halon_system_timeout=30
self_destruct_countdown=60
escape_pods=2

[usa:children]
southeast
northeast
southwest
northwest
								
							

Hechos / Facts

  • Los Hechos o "Facts" son variables de información que surgen de una conversación con un host (local o remoto), por ejemplo:
    • Dirección IP
    • tipo o distribución del sistema operativo
  • Estos podrán ser usados posteriormente en Playbooks para realizar condicionales, agrupamientos, etc.
  • Los hechos se almacenan comúnmente en archivos INI (pueden ser también JSON)

Hechos / Facts

Por ejemplo en el siguiente archivo local INI:
									
[general]
asdf=1
bar=2
								
							
Se define un hecho llamado general con dos miembros: asdf y bar. Al ser local, podemos acceder a este hecho en nuestro playbook posteriormente refieriéndonos a el así:
								
{{ ansible_local.preferences.general.asdf }}
								
							

Módulos

Un Módulo o Module son pequeños programas o plugins que al ejecutarse realizan las acciones deseadas sobre algún Host. Podemos verlas como la unidad funcional más pequeña de Ansible, ya que cualquier instrucción en Ansible por lo general resulta en la ejecución de uno o varios Módulos. Estos se almacenan en bibliotecas y Ansible por defecto viene con una gran biblioteca que contiene los más comunes. Pero esta biblioteca puede crecer instalando nuevos módulos disponibles en internet o realizando los propios.

Módulos

Desde la línea de comando, podemos ejecutar estos tres módulos:
								
ansible webservers -m service -a "name=httpd state=started"
ansible webservers -m ping
ansible webservers -m command -a "/sbin/reboot -t now"
								
							
Algunos módulos pueden recibir varios argumentos que se ingresan escribiendo -a.

Tareas

Una tarea, en su nivel más básico, no es más que un llamado a un Módulo de Ansible. Estas se emplearán para la composición de Plays o "jugadas".

Playbooks

  • Son el conjunto base de instrucciones que me permitirá administrar las distintas configuraciones de mi infraestructura, automatizar y orquestrar tareas.
  • Técnicamente hablando, son un conjunto de Plays o "jugadas". Las jugadas no son más que decirle a un Host o Grupo de Hosts que ejecuten ciertas Roles o Tareas.
  • Un Playbook se escribe en un archivo YML.

Playbooks

								
---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum: name=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running (and enable it at boot)
    service: name=httpd state=started enabled=yes
								
							

Ejemplo: Docker + Zookeeper

Estructura de Carpetas: Ansible Best Practices

Estructura de Carpetas: Ansible Best Practices

Estructura de Carpetas: Ansible Best Practices

Inventarios

  • Desarrollo
  • QA Seven (Amazon)
  • QA Cliente
  • Producción

Inventario Desarrollo

							
# file: dev

[local]
127.0.0.1

[damontic]
192.168.16.8

[rodrigo]
192.168.16.9

[oscar]
192.168.6.90

[vashy]
192.168.16.6

[natalia]
192.168.16.7

[archlinux:children]
damontic

[ubuntu:children]
rodrigo
oscar
vashy
natalia

[dev:children]
damontic
rodrigo
oscar
vashy
natalia

[dockers:children]
local

[zookeeper1:children]
local

[zookeeper2:children]
local

[kafka1:children]
local

[kafka2:children]
local

[sparkMaster1:children]
local

[sparkMaster2:children]
local

[sparkWorker1:children]
local

[sparkWorker2:children]
local

[cassandra1:children]
local

[cassandra2:children]
local

[cassandra3:children]
local

[cassandraClient:children]
local

[builder:children]
local

[zookeepers:children]
zookeeper1
zookeeper2

[kafkas:children]
kafka1
kafka2

[sparks:children]
sparkMaster1
sparkMaster2
sparkWorker1
sparkWorker2

[cassandras:children]
cassandra1
cassandra2
cassandra3

[ingestor1:children]
local

[ingestores:children]
ingestor1
						    
						

Inventario QA Seven

						
file: qaSeven

[local]
127.0.0.1

[qa1]
52.90.45.171

[qa2]
54.84.125.14

[qa3]
54.86.84.38

[qaSeven:children]
qa1
qa2
qa3

[dockers:children]
qa1
qa2
qa3

[zookeeper1:children]
qa1

[zookeeper2:children]
qa2

[kafka1:children]
qa3

[kafka2:children]
qa1

[sparkMaster1:children]
qa2

[sparkMaster2:children]
qa3

[sparkWorker1:children]
qa1

[sparkWorker2:children]
qa2

[ingestor1:children]
qa3

[zookeepers:children]
zookeeper1
zookeeper2

[kafkas:children]
kafka1
kafka2

[sparks:children]
sparkMaster1
sparkMaster2
sparkWorker1
sparkWorker2

[ingestores:children]
ingestor1
						    
						

Hechos / Facts

							
[david@JANUS ~] ansible -i dev -m setup vashy
					    	
						

Variables de Grupo

							
# file: group_vars/dev
netcat_command: nc
spark_version: 1.6.0
scala_version: 2.11
hadoop_version: 2.6
kafka_version: 0.9.0.0
cassandra_seeds: 172.17.0.8
cassandra_max_heap_size: 2G
cassandra_heap_new_size: 1G
					    	
						

Variables de Host

							
# file: host_vars/david
ansible_ssh_user: david
infra_git_clone_dir: /home/david/volume/donde_hice_clone/proyecto_infra
cassandra_installation_dir: /home/david/volume/Java/cassandra/approyectoe-cassandra-3.0.1
netcat_command: ncat
					    	
						

Despliegue: playbook principal

							
# file: site.yml
- include: dependencias.yml
- include: zookeepers.yml
- include: kafkas.yml
- include: sparks.yml
- include: cassandras.yml
- include: ingestor.yml
					    	
						

Despliegue: playbook dependencias.yml

							
# file: dependencias.yml

- hosts: dockers
  roles:
    - {
       role: dependencias_docker
    }
					    	
						

Despliegue: rol dependencias_docker

							
# file: roles/dependencias_docker/tasks/main.yml

- name: make sure python2-httplib2 is installed in Ubuntu
  apt: name=python-httplib2 state=present
  when: ansible_distribution == "Ubuntu"

- name: make sure python2-httplib2 is installed in Archlinux
  pacman: name=python2-httplib2 state=present
  when: ansible_distribution == "Archlinux"

- name: be sure docker is installed in Ubuntu
  apt: name=docker-engine state=present
  tags: docker
  when: ansible_distribution == "Ubuntu"

- name: be sure docker is installed in Archlinux
  pacman: name=docker state=present
  tags: docker
  when: ansible_distribution == "Archlinux"

- name: be sure dockerd is running and enabled in System Distributions
  service: name=docker.service state=started
  tags: docker
  when: ansible_distribution == "Archlinux" or (ansible_distribution == "Ubuntu" and (ansible_distribution_version == "15.10" or ansible_distribution_version == "15.04")) or (ansible_distribution == "RedHat" and ansible_distribution_major_version == "7" )

- name: be sure dockerd is running and enabled in init based Distributions
  service: name=docker state=started
  tags: docker
  when: ansible_distribution == "Ubuntu" and (ansible_distribution_version == "14.10" or ansible_distribution_version == "14.04" or ansible_distribution_version == "13.10" or ansible_distribution_version == "13.04") or (ansible_distribution == "RedHat" and ansible_distribution_major_version == "6" )

- name: create directory /opt/s4n
  file: path="/opt/s4n" state=directory owner="{{ ansible_ssh_user }}" group="{{ ansible_ssh_user }}"
  when: ansible_distribution == "RedHat"

- name: create directory /opt/s4n/facturacion
  file: path="/opt/s4n/facturacion" state=directory owner="{{ ansible_ssh_user }}" group="{{ ansible_ssh_user }}"
  when: ansible_distribution == "RedHat"

- name: create directory /opt/s4n/facturacion/docker_images
  file: path="/opt/s4n/facturacion/docker_images" state=directory owner="{{ ansible_ssh_user }}" group="{{ ansible_ssh_user }}"
  when: ansible_distribution == "RedHat"

- name: send infra-proyecto.tar.gz to RedHats
  copy: src="{{ deploy_directory }}/infra-proyecto/infra-proyecto.tar.gz" dest="/opt/s4n/facturacion/infra-proyecto.tar.gz" owner="{{ ansible_ssh_user }}" group="{{ ansible_ssh_user }}" mode="a=r"
  when: ansible_distribution == "RedHat"

- name: send alpine-image to RedHats
  copy: src="{{ deploy_directory }}/docker-images/alpine.tar" dest="/opt/s4n/facturacion/docker_images/alpine.tar" owner="{{ ansible_ssh_user }}" group="{{ ansible_ssh_user }}" mode="a=r"
  when: ansible_distribution == "RedHat"

- name: send alpine-oracle-jre to RedHats
  copy: src="{{ deploy_directory }}/docker-images/proyecto_alpine-oracle-jre-8.tar" dest="/opt/s4n/facturacion/docker_images/proyecto_alpine-oracle-jre-8.tar" owner="{{ ansible_ssh_user }}" group="{{ ansible_ssh_user }}" mode="a=r"
  when: ansible_distribution == "RedHat"

- name: verifies that alpine exists in RedHat
  command: "docker images alpine"
  register: alpineImageExists
  when: ansible_distribution == "RedHat"

- name: docker import alpine to RedHats
  command: "docker load -i=/opt/s4n/facturacion/docker_images/alpine.tar"
  when: ansible_distribution == "RedHat" and alpineImageExists.stdout.find('alpine') == -1

- name: verifies that alpine-oracle-jre-8 exists in RedHat
  command: "docker images proyecto/alpine-oracle-jre-8"
  register: oracleImageExists
  when: ansible_distribution == "RedHat"

- name: docker import alpine-oracle-jre to RedHats
  command: "docker load -i=/opt/s4n/facturacion/docker_images/proyecto_alpine-oracle-jre-8.tar"
  when: ansible_distribution == "RedHat"  and oracleImageExists.stdout.find('oracle') == -1

- name: be sure docker alpine-oracle-jre-8 is available
  docker_image: path={{ infra_proyecto_git_clone_dir }}/alpine-oracle-jre-8 name=proyecto/alpine-oracle-jre-8 state=present
  tags: docker
					    	
						

Despliegue: playbook zookeepers.yml

							
# file: zookeepers.yml
hosts: zookeeper1
  roles:
    - {
    	role: zookeeper,
    	zookeeper_zk_id: 1,
    	zookeeper_zk_servers: "server.1=0.0.0.0:2888:3888 server.2={{ hostvars[groups['zookeeper2'][0]]['inventory_hostname'] }}:2889:3889",
    	zookeeper_host_client_port: 2181,
    	zookeeper_host_quorum_port: 2888,
    	zookeeper_host_leader_election_port: 3888
    }

- hosts: zookeeper2
  roles:
    - {
    	role: zookeeper,
    	zookeeper_zk_id: 2,
    	zookeeper_zk_servers: "server.1={{ hostvars[groups['zookeeper1'][0]]['inventory_hostname'] }}:2888:3888 server.2=0.0.0.0:2888:3888",
    	zookeeper_host_client_port: 2182,
    	zookeeper_host_quorum_port: 2889,
    	zookeeper_host_leader_election_port: 3889
    }
					    	
						

Despliegue: rol zookeepers

							
#file: roles/zookeeper/tasks/main.yml

- name: send alpine-zookeeper to RedHats
  copy: src="{{ deploy_directory }}/docker-images/proyecto_alpine-zookeeper.tar" dest="/opt/s4n/facturacion/docker_images/proyecto_alpine-zookeeper.tar" owner="{{ ansible_ssh_user }}" group="{{ ansible_ssh_user }}" mode="a=r"
  when: ansible_distribution == "RedHat"

- name: verifies that alpine-zookeeper exists in RedHat
  command: "docker images proyecto/alpine-zookeeper"
  register: alpineZookeeperImageExists
  when: ansible_distribution == "RedHat"

- name: docker load alpine-zookeeper to RedHats
  command: "docker load -i=/opt/s4n/facturacion/docker_images/proyecto_alpine-zookeeper.tar"
  when: ansible_distribution == "RedHat" and alpineZookeeperImageExists.stdout.find('zookeeper') == -1

- name: be sure docker alpine-zookeeper is available
  docker_image: path={{ infra_proyecto_git_clone_dir }}/alpine-zookeeper name=proyecto/alpine-zookeeper state=present
  tags: docker

- name: run the zookeeper node {{ zookeeper_zk_id }}
  docker:
    name: zoo_{{ zookeeper_zk_id }}
    image: proyecto/alpine-zookeeper
    state: started
    env:
      ZK_SERVERS: "{{ zookeeper_zk_servers }}"
      ZK_ID: "{{ zookeeper_zk_id }}"
    expose:
      - 2181
      - 2888
      - 3888
    ports:
      - "{{ zookeeper_host_client_port }}:2181"
      - "{{ zookeeper_host_quorum_port }}:2888"
      - "{{ zookeeper_host_leader_election_port }}:3888"
    docker_api_version: 1.18
    restart_policy: on-failure
    restart_policy_retry: 3
					    	
						

Conclusiones

  • Ansible es Push-Based.
  • Facilita el despliegue en cualquier Ambiente.
    • (D'_')=D (Producción)
  • Debemos aprender a no depender de Internet para las instalaciones.
  • Hay más: ansible-galaxy